package kr.pe.osdev.common.xml;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;

import com.sun.org.apache.xml.internal.serialize.OutputFormat;
import com.sun.org.apache.xml.internal.serialize.XMLSerializer;

public class XmlUtil {

    private static Hashtable<String, Object> cache = new Hashtable<String, Object>();

    public static Document createDocument() {
        try {
            DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            
            return builder.newDocument();
        } catch (ParserConfigurationException e) {
        }

        return null;
    }

    public static String transform(String xmlString, String xslFilePath) throws TransformerConfigurationException, TransformerException {
        TransformerFactory factory = null;
        Transformer transformer = null;

        System.setProperty("javax.xml.transform.TransformerFactory", "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");

        if (cache.containsKey(xslFilePath)) {
            transformer = (Transformer) cache.get(xslFilePath);
        } else {
            factory = TransformerFactory.newInstance();
            transformer = factory.newTransformer(new SAXSource(new InputSource(xslFilePath)));
            cache.put(xslFilePath, transformer);
        }
        
        StringWriter sw = new StringWriter();

        transformer.transform(new SAXSource(new InputSource(new StringReader(xmlString))), new StreamResult(sw));

        if (sw != null)
            return sw.toString();
        else
            return null;
    }

//    public static Node findNode(Node root, String path) throws Exception {
//        return XPathAPI.selectSingleNode(root, path);
//    }

    private static String generateDocumentToXml(Document document) {
        ByteArrayOutputStream baos = null;
        String xml = null;

        try {
            TransformerFactory tranFactory = TransformerFactory.newInstance();
            Transformer transformer = tranFactory.newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");

            baos = new ByteArrayOutputStream();

            Source xmlSource = new DOMSource(document);
            Result outputTarget = new StreamResult(baos);
            transformer.transform(xmlSource, outputTarget);

            xml = new String(baos.toByteArray(), 0, baos.size(), "UTF-8");
        } catch (Exception e) {
        } finally {
            try {
                if(baos != null)
                    baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        return xml;
    }

    public static Element createElement(Document document, String name, String value) {
        Element el = document.createElement(name);
        
        if (value != null)
            el.appendChild(document.createTextNode(value));

        return el;
    }

    public static void addSingleElement(Document doc, Element body, Map<String, String> map) {
        Element info = doc.createElement("info");
        body.appendChild(info);

        Set<String> set = map.keySet();
        Iterator<String> iter = set.iterator();

        while (iter.hasNext()) {
            String innerKey = (String) iter.next();

            String innerVal = null;
            innerVal = String.valueOf(map.get(innerKey));

            if (innerVal != null && innerVal.length() > 0 && !innerVal.equals("null")) {
                info.appendChild(XmlUtil.createElement(doc, innerKey, innerVal));
            }
        }
    }

    public static void addElement(XmlDocument xmlDocument, ArrayList<Map<String, String>> list, String elementName) {
        Document document = xmlDocument.getDocument();
        Element lists = xmlDocument.getDocument().createElement(elementName);
        xmlDocument.getBody().appendChild(lists);
        xmlDocument.getBody().appendChild(addElement(document, lists, list));
    }

    private static Element addElement(Document document, Element lists, ArrayList<Map<String, String>> list) {
        for (Map<String, String> map : list) {
            Element elementList = document.createElement("list");

            Iterator<String> iterator = map.keySet().iterator();

            while (iterator.hasNext()) {
                String key = iterator.next();
                String value = map.get(key);

                if (value != null && value.length() > 0) {
                    elementList.appendChild(XmlUtil.createElement(document, key, value));
                }
            }
            
            lists.appendChild(elementList);
        }
        
        return lists;
    }

    public static String serialize(Node node) {
        OutputFormat format = new OutputFormat();
        StringWriter result = new StringWriter();

        try {
            XMLSerializer serializer = new XMLSerializer(result, format);
            switch (node.getNodeType()) {
            case Node.DOCUMENT_NODE:
                serializer.serialize((Document) node);
                break;
            case Node.ELEMENT_NODE:
                serializer.serialize((Element) node);
                break;
            case Node.DOCUMENT_FRAGMENT_NODE:
                serializer.serialize((DocumentFragment) node);
                break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return result.toString();
    }

    public static Object queryXpath(Document doc, QName type, String query) throws Exception {
        XPathFactory factory = XPathFactory.newInstance();
        XPath xpath = factory.newXPath();
        XPathExpression expr = xpath.compile(query);

        if (type == null)
            return expr.evaluate(doc);
        else
            return expr.evaluate(doc, type);
    }
    
    public static String generateXml(ArrayList<Map<String, String>> list) throws Exception {
        XmlDocument xmlDocument = new XmlDocument();
        XmlUtil.addElement(xmlDocument, list, "lists");            
        
        return XmlUtil.generateDocumentToXml(xmlDocument.getDocument());
    }    

    public static String generateXml(Map<String, String> data) throws Exception {
        XmlDocument xmlDocument = new XmlDocument();
        XmlUtil.addSingleElement(xmlDocument.getDocument(), xmlDocument.getBody(), data);            

        return XmlUtil.generateDocumentToXml(xmlDocument.getDocument());
    }
    
    public static String generateInfoXml(ArrayList<Map<String, String>> list) {
        XmlDocument xmlDom = null;
        String xml = null;

        try {
            xmlDom = new XmlDocument();
            Map<String, String> map = new HashMap<String, String>();
            
            for(Map<String, String> temp : list){
                map.putAll(temp);
            }
            
            XmlUtil.addSingleElement(xmlDom.getDocument(), xmlDom.getBody(), map);            
            xml = XmlUtil.generateDocumentToXml(xmlDom.getDocument());
        } catch (Exception e) {
            e.printStackTrace();
        }

        return xml;
    }
}